﻿using System;
using System.Collections.Generic;
using System.Reflection;

namespace Gonte.Reflection
{
    public static class TypeHelper
    {
        /// <summary>
        /// Tests whether the type is a nullable one
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsNullable(Type type)
        {
            return type.IsGenericType
                && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
        }

        /// <summary>
        /// Unboxes the type from collections and nullable types
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static Type UnwrapType(Type type)
        {
            if (IsNullable(type))
            {
                type = type.GetGenericArguments()[0];
            }
            else if (type.IsGenericType
                && type.GetGenericTypeDefinition().Equals(typeof(KeyValuePair<,>)))
            {
                type = type.GetGenericArguments()[1];
            }
            else if (null != type.GetInterface("ICollection"))
            {
                type = CollectionHelper.GetItemType(type);

                if (IsNullable(type)) // The collection item can be nullable
                {
                    type = type.GetGenericArguments()[0];
                }
            }

            return type;
        }

        /// <summary>
        /// Tests whether the property name of the given type is hiding it base property (with the keyword new in C#)
        /// </summary>
        /// <param name="type">The type to test</param>
        /// <param name="propertyName">The name of the property to test</param>
        /// <returns>True if the type is hiding the property of the base class, otherwise it returns false</returns>
        public static bool IsHidingBaseProperty(Type type, string propertyName)
        {
            PropertyInfo derivedPropertyInfo = type.GetProperty(propertyName);

            ExceptionThrower.ThrowWhen(derivedPropertyInfo == null, string.Format("There is no property of name: '{0}' in type: '{1}'", propertyName, type.FullName));

            Type baseType = derivedPropertyInfo.DeclaringType.BaseType;
            PropertyInfo basePropertyInfo = baseType.GetProperty(derivedPropertyInfo.Name, derivedPropertyInfo.PropertyType);

            if (basePropertyInfo == null)
            {
                return false;
            }

            if (basePropertyInfo.DeclaringType == derivedPropertyInfo.DeclaringType)
            {
                return false;
            }

            MethodInfo baseMethodDefinition = basePropertyInfo.GetGetMethod().GetBaseDefinition();
            MethodInfo derivedMethodDefinition = derivedPropertyInfo.GetGetMethod().GetBaseDefinition();

            return baseMethodDefinition.DeclaringType != derivedMethodDefinition.DeclaringType;
        }
    }
}
